package com.ponhu.ea2.stockcenter.provider.service.rpc.impl;

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.ponhu.ea2.common.basic.model.PagedResult;
import com.ponhu.ea2.stockcenter.dto.StockQueryListDTO;
import com.ponhu.ea2.stockcenter.enums.DefinedSystem;
import com.ponhu.ea2.stockcenter.provider.constants.CommonConstant;
import com.ponhu.ea2.stockcenter.provider.entity.*;
import com.ponhu.ea2.stockcenter.provider.mapper.GWallDataMapper;
import com.ponhu.ea2.stockcenter.provider.mapper.StockInventoryRealMapper;
import com.ponhu.ea2.stockcenter.provider.mapper.StockWarehouseRelationMapper;
import com.ponhu.ea2.stockcenter.provider.service.biz.stockinit.StockDataInitService;
import com.ponhu.ea2.stockcenter.provider.service.entity.*;
import com.ponhu.ea2.stockcenter.provider.support.service.RedisSupportService;
import com.ponhu.ea2.stockcenter.provider.utils.CommonUtils;
import com.ponhu.ea2.stockcenter.provider.utils.DataConverterUtils;
import com.ponhu.ea2.stockcenter.provider.utils.ProBeanUtils;
import com.ponhu.ea2.stockcenter.provider.utils.RedisUtils;
import com.ponhu.ea2.stockcenter.query.ApplyFreeQuery;
import com.ponhu.ea2.stockcenter.service.bms.StockCommonService;
import com.ponhu.ea2.stockcenter.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@DubboService(version = "1.0.0", interfaceClass = StockCommonService.class, timeout = 60000)
public class StockCommonServiceImpl implements StockCommonService {

    @Autowired
    private StockInventoryRealMapper stockInventoryRealMapper;

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedisSupportService redisSupportService;
    @Autowired
    private StockDataInitService stockDataInitService;
    @Autowired
    private StockWarehouseService stockWarehouseService;
    @Autowired
    private StockInventoryRealService stockInventoryRealService;
    @Autowired
    private StockInventoryUnrealService stockInventoryUnrealService;
    @Autowired
    private StockWarehouseRelationService stockWarehouseRelationService;
    @Autowired
    private StockInventoryDocOccupyService stockInventoryDocOccupyService;
    @Autowired
    private StockInventoryWarehouseTraceService stockInventoryWarehouseTraceService;

    @Resource
    private GWallDataMapper gWallDataMapper;
    @Resource
    private StockWarehouseRelationMapper stockWarehouseRelationMapper;

    @Override
    public ReturnResponseBean<String> getStockRequestToken(DefinedSystem definedSystem) {
        try {
            String uuid = CommonUtils.getUUID();
            String stockRequestToken = RedisUtils.getRedisKey(definedSystem.toString(), uuid);

            String redisKey = RedisUtils.getRedisKey(CommonConstant.IDEMPOTENCE_PREFIX, stockRequestToken);
            RBucket<String> rBucket = redissonClient.getBucket(redisKey, StringCodec.INSTANCE);

            if (redisSupportService.bucketIsExists(CommonConstant.WITHOUT_REDIS_LOCK))
                rBucket.trySet(redisKey, CommonConstant.IDEMPOTENCE_EXPIRED_SECONDS_INIT, TimeUnit.SECONDS);
            else rBucket.trySet(redisKey, CommonConstant.IDEMPOTENCE_EXPIRED_SECONDS, TimeUnit.SECONDS);

            log.info("获取Token ===>> token:{} ===>> time:{}", stockRequestToken, LocalTime.now());
            return new ReturnResponseBean<>(stockRequestToken);
        } catch (Exception e) {
            String token = RedisUtils.getRedisKey(CommonConstant.IDEMPOTENCE_PREFIX,
                    definedSystem.toString(), CommonConstant.IDEMPOTENCE_UUID);
            return new ReturnResponseBean<>(token);
        }
    }

    @Override
    public ReturnResponseBean<List<OrderOccupyStockInfoVO>> getOrderOccupyStockInfo(String orderNo) {
        //查询单据占用数据列表
        List<StockInventoryDocOccupy> inventoryDocOccupyList = stockInventoryDocOccupyService.getAllDocOccupyRecord(orderNo);

        if (CollectionUtils.isEmpty(inventoryDocOccupyList)) {
            log.error("销售层：订单号未查询到任何货品库存信息");
            return ReturnResponseBean.ok(new ArrayList<>());
        }

        //获取单据下所有库存编码列表
        List<String> inventoryCodeList = inventoryDocOccupyList.stream().map(StockInventoryDocOccupy::getInventoryCode)
                .collect(Collectors.toList());

        return redisSupportService.wrapWithReadLock(inventoryCodeList, () -> {
            //获取货品占用库存数据列表
            List<StockInventoryDocOccupy> docOccupyList = stockInventoryDocOccupyService.getAllDocOccupyRecord(inventoryCodeList);

            //组装返回数据
            List<OrderOccupyStockInfoVO> collect = docOccupyList.stream().map(d -> {
                OrderOccupyStockInfoVO stockInfoVO = new OrderOccupyStockInfoVO();
                stockInfoVO.setInventoryCode(d.getInventoryCode());
                stockInfoVO.setOccupyStockNum(d.getUsedNum());
                return stockInfoVO;
            }).collect(Collectors.toList());
            log.info("销售层：根据订单号获取订单下货品占用库存数成功");
            return new ReturnResponseBean<>(collect);
        });
    }

    /**
     * 初始化库存中心数据
     */
    @Override
    public void initStockData() {
        stockDataInitService.initStockData();
    }

    /**
     * 条件查询全部货品库存列表
     *
     * @return
     */
    @Override
    public PagedResult<StockQueryListVO> getAllInventoryStocks(StockQueryListDTO queryDTO) {

        String redisKey = RedisUtils.getRedisKey(CommonConstant.GET_ALL_INVENTORY_STOCKS, queryDTO.toString());
        Long total = redisSupportService.getObjFromRedis(redisKey, () ->
                stockInventoryRealMapper.countAllInventoryStocks(queryDTO), Long.class, 1, TimeUnit.MINUTES);

        Integer offset = (queryDTO.getPageIndex() - 1) * queryDTO.getPageSize();
        List<StockQueryListVO> allInventoryStocks = stockInventoryRealMapper
                .getAllInventoryStocks(queryDTO, offset, queryDTO.getPageSize());
        if (CollectionUtils.isEmpty(allInventoryStocks))
            return new PagedResult<>(Lists.newArrayList(), queryDTO.getPageIndex(), total, queryDTO.getPageSize());

        List<String> inventoryCodes = allInventoryStocks.stream()
                .map(StockQueryListVO::getInventoryCode).collect(Collectors.toList());

        List<StockWarehouseRelation> stockRelations = stockWarehouseRelationService
                .getRelationsByInventoryCodes(inventoryCodes);
        Map<String, Integer> realStockMap = stockRelations.parallelStream()
                .collect(Collectors.groupingBy(StockWarehouseRelation::getInventoryCode,
                        Collectors.mapping(StockWarehouseRelation::getRealStockNum, Collectors.reducing(0, Integer::sum))));
        Map<String, Integer> lockedStockMap = stockRelations.parallelStream()
                .collect(Collectors.groupingBy(StockWarehouseRelation::getInventoryCode,
                        Collectors.mapping(StockWarehouseRelation::getLockedStockNum, Collectors.reducing(0, Integer::sum))));

        Map<String, Integer> tracesMap = stockInventoryWarehouseTraceService
                .getAllTracesOccupyToMap(inventoryCodes, null);

        Map<String, Integer> unRealStockMap = stockInventoryUnrealService.getUnRealInventoryStockList(inventoryCodes)
                .stream().collect(Collectors.toMap(StockInventoryUnreal::getInventoryCode, StockInventoryUnreal::getSaleStockNum));

        Map<String, String> whMap = getAllWarehouseInfos().stream().collect(Collectors
                .toMap(WarehouseInfoVo::getWhCode, WarehouseInfoVo::getWhName));

        Map<String, List<WhStockQueryListVo>> whStocksMap = stockRelations.parallelStream()
                .collect(Collectors.groupingBy(StockWarehouseRelation::getInventoryCode,
                        Collectors.mapping(stock -> new WhStockQueryListVo()
                                .setWhCode(stock.getWarehouseCode())
                                .setWhName(whMap.get(stock.getWarehouseCode()))
                                .setRealStockNum(stock.getRealStockNum())
                                .setLockedStockNum(stock.getLockedStockNum()), Collectors.toList())));

        allInventoryStocks.stream().forEach(stockVo -> {
            Integer unReal = unRealStockMap.getOrDefault(stockVo.getInventoryCode(), 0);
            stockVo.setSaleStockNum(stockVo.getSaleStockNum() + unReal);

            Integer occupyNum = tracesMap.getOrDefault(stockVo.getInventoryCode(), 0);
            stockVo.setWhStockQueryListVos(whStocksMap.get(stockVo.getInventoryCode()))
                    .setRealStockNum(realStockMap.getOrDefault(stockVo.getInventoryCode(), 0))
                    .setLockedStockNum(lockedStockMap.getOrDefault(stockVo.getInventoryCode(), 0) + occupyNum);
        });

        PagedResult<StockQueryListVO> pagedResult = new PagedResult<>(allInventoryStocks,
                queryDTO.getPageIndex(), total, queryDTO.getPageSize());
        pagedResult.setPageSize(queryDTO.getPageSize());
        return pagedResult;
    }

    /**
     * 查询指定货品编码的销售库存占用
     *
     * @return
     */
    @Override
    public PagedResult<StockSaleOccupyVO> getSaleOccupyVosByCode(String inventoryCode, Integer pageIndex, Integer pageSize) {

        List<StockInventoryDocOccupy> occupyList = stockInventoryDocOccupyService
                .getAllDocOccupyRecord(Arrays.asList(inventoryCode));

        List<StockSaleOccupyVO> occupyVOList = occupyList.stream().skip((pageIndex - 1) * pageSize).limit(pageSize)
                .map(occupy -> ProBeanUtils.copyProperties(occupy, StockSaleOccupyVO.class)).collect(Collectors.toList());

        PagedResult pagedResult = new PagedResult(occupyVOList, pageIndex, (long) occupyList.size(), pageSize);
        pagedResult.setPageSize(pageSize);
        return pagedResult;
    }

    /**
     * 查询指定货品编码的实物库存占用
     *
     * @return
     */
    @Override
    public PagedResult<StockWhOccupyVO> getWhOccupyVosByCode(String inventoryCode, Integer pageIndex, Integer pageSize) {

        Map<String, Integer> tracesMap = stockInventoryWarehouseTraceService
                .getWhTracesOccupyToMap(inventoryCode);

        Map<String, String> whMap = getAllWarehouseInfos().stream().collect(Collectors
                .toMap(WarehouseInfoVo::getWhCode, WarehouseInfoVo::getWhName));

        List<StockWhOccupyVO> occupyVOList = stockWarehouseRelationService
                .getRelationsByInventoryCodes(Arrays.asList(inventoryCode)).stream()
                .filter(whStock -> whStock.getRealStockNum() > 0)
                .map(whStock -> new StockWhOccupyVO()
                        .setWhCode(whStock.getWarehouseCode())
                        .setInventoryCode(whStock.getInventoryCode())
                        .setTransferOccupy(whStock.getLockedStockNum())
                        .setWhName(whMap.get(whStock.getWarehouseCode()))
                        .setTraceOccupy(tracesMap.getOrDefault(whStock.getWarehouseCode(), 0)))
                .filter(occupyVO -> occupyVO.getTransferOccupy() > 0 || occupyVO.getTraceOccupy() > 0)
                .collect(Collectors.toList());

        List<StockWhOccupyVO> pageOccupyList = occupyVOList.stream()
                .skip((pageIndex - 1) * pageSize).limit(pageSize).collect(Collectors.toList());

        PagedResult pagedResult = new PagedResult(pageOccupyList, pageIndex, (long) occupyVOList.size(), pageSize);
        pagedResult.setPageSize(pageSize);
        return pagedResult;
    }

    /**
     * 获取全部仓库信息列表
     *
     * @return
     */
    @Override
    public List<WarehouseInfoVo> getAllWarehouseInfos() {
        try {
            return stockWarehouseService.list().stream().map(wh ->
                            new WarehouseInfoVo()
                                    .setWhName(wh.getWarehouseName())
                                    .setWhCode(wh.getWarehouseCode()))
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("调用WMS获取全部仓库信息集合报错 === error{}", e.getMessage());
            return Lists.newArrayList();
        }
    }

    /**
     * 查询指定货品的仓库位置和库存数量
     *
     * @param inventoryCodes
     * @return
     */
    @Override
    public ReturnResponseBean<List<InventoryStockWhVO>> getReallyStockInfo(Collection<String> inventoryCodes) {
        if (CollectionUtils.isEmpty(inventoryCodes))
            return ReturnResponseBean.ok(Lists.newArrayList());

        // 获取分布式读写锁的key值
        List<String> keys = inventoryCodes.stream().map(code -> RedisUtils
                .getRedisKey(CommonConstant.REDISSON_RW_LOCK_STOCK_GLOBAL, code)).collect(Collectors.toList());

        // 将查询放到读锁中
//        List<StockWarehouseRelation> stockWarehouseRelations = redisSupportService.wrapWithReadLock(keys,
//                () -> stockWarehouseRelationService.getRelationsByInventoryCodes(inventoryCodes));
        List<StockWarehouseRelation> stockWarehouseRelations =
                stockWarehouseRelationService.getRelationsByInventoryCodes(inventoryCodes);

        Map<String, Integer> freeMap = stockInventoryRealService.getRealInventoryStockList(inventoryCodes).parallelStream()
                .collect(Collectors.toMap(StockInventoryReal::getInventoryCode, StockInventoryReal::getMathFreeStockNum));

        Map<String, String> whMap = getAllWarehouseInfos().stream().collect(Collectors
                .toMap(WarehouseInfoVo::getWhCode, WarehouseInfoVo::getWhName));

        // 处理查询结果
        Map<String, List<StockWarehouseVO>> relationMap = stockWarehouseRelations.stream()
                .map(stock -> new StockWarehouseVO()
                        .setInventoryCode(stock.getInventoryCode())
                        .setRealStockNum(stock.getRealStockNum())
                        .setWhName(whMap.get(stock.getWarehouseCode())))
                .collect(Collectors.groupingBy(StockWarehouseVO::getInventoryCode));

        List<InventoryStockWhVO> stockWhVOS = freeMap.entrySet().stream().map(entry ->
                        new InventoryStockWhVO()
                                .setInventoryCode(entry.getKey())
                                .setFreeStockNum(entry.getValue())
                                .setWhVos(relationMap.getOrDefault(entry.getKey(), Lists.newArrayList())))
//                .filter(vo -> vo.getFreeStockNum() > 0 || CollectionUtils.isNotEmpty(vo.getWhVos()))
                .collect(Collectors.toList());

        return new ReturnResponseBean<>(stockWhVOS);
    }

    /**
     * 根据货品编码分页查询调拨在途明细数据
     *
     * @param query
     * @return
     */
    @Override
    public ReturnResponseBean<PagedResult<ApplyFreeStockVO>> getApplyFreeStockDetail(ApplyFreeQuery query) {
        //分页查询调拨在途数据
        Page<GWallApplyFreeStock> page = new Page<>();
        page.setCurrent(query.getPageIndex());
        page.setSize(query.getPageSize());
        Page<GWallApplyFreeStock> freeStockPage = gWallDataMapper.selectPage(page, Wrappers.lambdaQuery(GWallApplyFreeStock.class)
                .eq(GWallApplyFreeStock::getInventoryCode, query.getInventoryCode()));

        if (CollectionUtils.isEmpty(freeStockPage.getRecords())) {
            return ReturnResponseBean.ok(null);
        }

        //获取仓库数据
        List<StockWarehouse> warehouseDtoList = stockWarehouseService.list();
        Map<String, String> warehouseMap;
        if (CollectionUtils.isEmpty(warehouseDtoList)) {
            warehouseMap = new HashMap<>();
        } else {
            warehouseMap = warehouseDtoList.stream().collect(Collectors.toMap(StockWarehouse::getWarehouseCode, StockWarehouse::getWarehouseName));

        }

        List<ApplyFreeStockVO> stockVOList = freeStockPage.getRecords().stream().map(i -> {
            ApplyFreeStockVO applyFreeStockVO = new ApplyFreeStockVO();
            BeanUtils.copyProperties(i, applyFreeStockVO);
            //转换仓库编码
            applyFreeStockVO.setInWarehouseName(warehouseMap.get(i.getInWarehouseCode()));
            applyFreeStockVO.setOutWarehouseName(warehouseMap.get(i.getOutWarehouseCode()));
            return applyFreeStockVO;
        }).collect(Collectors.toList());

        return ReturnResponseBean.ok(DataConverterUtils.convertToPagedResult(freeStockPage, stockVOList));
    }

    @Override
    public ReturnResponseBean<List<InventorySalesStockInfo>> getSalesStockInfo(List<String> inventoryCodeList) {
        if (CollectionUtils.isEmpty(inventoryCodeList)) {
            return ReturnResponseBean.ok(null);
        }
        return ReturnResponseBean.ok(stockWarehouseRelationMapper.getSalesStockInfo(inventoryCodeList));
    }

    /**
     * 查询可卖库存信息：可售库存+虚拟可售库存-销售占用库存
     *
     * @param inventoryCodes
     * @return
     */
    @Override
    public List<StockMqVO> getSaleStockMqVoList(Collection<String> inventoryCodes) {
        if (CollectionUtils.isEmpty(inventoryCodes)) return Lists.newArrayList();

        List<StockInventoryReal> realStockList = stockInventoryRealService.getRealInventoryStockList(inventoryCodes);

        Map<String, Integer> unrealMap = stockInventoryUnrealService.getUnRealInventoryStockList(inventoryCodes).stream()
                .collect(Collectors.toMap(StockInventoryUnreal::getInventoryCode, StockInventoryUnreal::getSaleStockNum));

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return realStockList.parallelStream().map(stock -> ProBeanUtils.copyProperties(stock, StockMqVO.class)
                .setStock(stock.getMathSaleStockNum(unrealMap.get(stock.getInventoryCode())))
                .setUpdatedTime(formatter.format(LocalDateTime.now()))).collect(Collectors.toList());
    }

    @Override
    public String getValueFromRedisBucket(String redisKey, Boolean getAndDel) {
        if (getAndDel) return redisSupportService.bucketGetAndDel(redisKey, String.class);
        else return redisSupportService.bucketGet(redisKey, String.class);
    }

    @Override
    public Boolean testRedisKeyEvent(String redisKey, String redisValue, Long timeToLive) {
        redisSupportService.bucketSet(redisKey, redisValue, timeToLive, TimeUnit.SECONDS);
        return true;
    }

}

